#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <ctype.h>

#define BUFLEN          1024

#define GET             0X55
#define PUT             0XAA
#define DELETE          0XFF
#define EXIT            1
#define OPEN            2
#define CLOSE           4

#define erraise(string) do{\
    perror(string);\
    exit(-1);\
}while(0)

#define donothing do{}while(0)
typedef struct
{
    int type;
    int key_length;
    int value_length;
    int filling;
}PACKET_HEADER;

static int sockfd;
static struct sockaddr_in servaddr;
static int set = 0, connected = 0;


static void print_help(char *buffer);
static void print_usage(char *buffer);

static int com_deal(char *command, char *prmt1, char *prmt2);
static int set_address(int argc, char **argv);
static int new_connection(char *ip, char *port);

int main(int argc, char **argv)
{
    int  n, type;
    char buffer[BUFLEN];
    char prmt1[128];
    char prmt2[1024];
    
    set = set_address(argc, argv);
    
    if (set)
    {
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            erraise("open socket error:");
 
        if ((connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0)
            erraise("connect to server failed:");
        connected = 1;
        printf("Connected to %s...\n", argv[1]);
    }

    for (; ;)
    {
        printf(">>> ");
        memset(buffer, 0, sizeof(buffer));
        fgets(buffer, BUFLEN, stdin);
        type = com_deal(buffer, prmt1, prmt2);
        if (type > 0)
        {
            if (OPEN == type)
            {
                if (1 == connected)
                {
                    close(sockfd);
                    connected = 0;
                    printf("Current connection closed...\n");
                } 
                
                if (1 == (new_connection(prmt1, prmt2)))
                    printf("Connected to %s...\n", prmt1);
            }
            
            if (CLOSE == type)
            {
                close(sockfd);
                connected = 0;
            }
            
            if (EXIT == type)
            {
                exit(0);
            }
            else if (PUT == type || GET == type || DELETE == type) 
            {
                if (connected == 1)
                {
                    PACKET_HEADER ph;
                    ph.type=type;
                    ph.key_length=strlen(prmt1);
                    ph.value_length=(type == PUT ? strlen(prmt2) : 0);
                    ////printf("key_length: %d\tvalue_length %d\n", ph.key_length, ph.value_length);
                    ph.filling=0;
                    write(sockfd, (void *)&ph, sizeof(ph));
                    memset(buffer, 0, sizeof(buffer));
                    strncpy(buffer, prmt1, ph.key_length);
                    if (ph.value_length != 0)
                        strncpy(buffer+ph.key_length, prmt2, ph.value_length);
                    int total_length = ph.key_length+ph.value_length;
                    n = write(sockfd, buffer, total_length);
                    if (GET == type)
                    {
                        memset(buffer, 0, sizeof(buffer));
                        n = read(sockfd, buffer, sizeof(buffer));
                        if (n > 4)
                            printf("%s\n", buffer+4);
                    
                    }
                }
                else 
                    printf("Not connected to a server!\n");
            }
        }
        else 
        {
            print_help(buffer);
            printf("%s", buffer);
        }
    }
    return 0;
}

static int new_connection(char *ip, char *port)
{
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        erraise("open socket error:");

    servaddr.sin_family = AF_INET;
    if (strlen(port) > 1)
    {
        int port_num = atoi(port);
        if (port_num > 65536 || port_num < 0)
        {
            printf("Illegal port!\n");
        }
        servaddr.sin_port = htons(port_num);
    }
    else 
        servaddr.sin_port = htons(8080);
    
    if (1 != inet_pton(AF_INET, ip, &(servaddr.sin_addr)))
    {
        printf("Illegal ip!\n");
        return 0;
    }

    if ((connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0)
        erraise("connect to server failed:");
    connected = 1;
    
    return 1;
}

static int set_address(int argc, char **argv)
{
    char servip[16];
    char buffer[1024];
    int set = 0;
    
    memset(&servaddr, 0, sizeof(servaddr));
    memset(servip, 0, sizeof(servip));
    memset(buffer, 0, sizeof(buffer));

    servaddr.sin_family = AF_INET;
    
    if (argc > 2)
    {   
        int port = atoi(argv[2]);
        
        if (port > 65535 || port < 0)
        {
            print_usage(buffer);
            printf("%s", buffer);
            exit(-1);
        }
        servaddr.sin_port = htons(port);
    }
    else 
    {
        servaddr.sin_port = htons(8080);
    }
    
    if (argc > 1)
    {
        strncpy(servip, argv[1], 16);
        if (1 != inet_pton(AF_INET, servip, &(servaddr.sin_addr)))
        {
            printf("Illegal parameter!\n");
            print_usage(buffer);
            printf("%s", buffer);
            exit(-1);
        } 
        set = 1;
    }

    return set;
}

static void print_usage(char *buffer)
{
    memset(buffer, 0, sizeof(buffer));
    sprintf(buffer+strlen(buffer), "Usage: \tkvsclient [SERVER_ADDRESS [SERVER PORT]]\n");
}

static void print_help(char *buffer)
{
    memset(buffer, 0, sizeof(buffer));
    sprintf(buffer+strlen(buffer), "List of classes of commands\n\n");
    sprintf(buffer+strlen(buffer), "Command for connection:\n\n");
    sprintf(buffer+strlen(buffer), "\tOPEN IP [PORT]\tConnect to server\n");
    sprintf(buffer+strlen(buffer), "\tCLOSE\t\tClose connection to server\n");
    sprintf(buffer+strlen(buffer), "\tEXIT\t\tExit the program\n\n");
    sprintf(buffer+strlen(buffer), "Command for dealing data records, format: COMMAND [key [value]]\n\n");
    sprintf(buffer+strlen(buffer), "\tPUT\t\tPut a key-value-pair into storage system\n");
    sprintf(buffer+strlen(buffer), "\tGET\t\tGet a record with the given key, value will be ignored if given\n");
    sprintf(buffer+strlen(buffer), "\tDELETE\t\tDelete a record with the given key, value will be ignored if given\n");
    sprintf(buffer+strlen(buffer), "\tEXIT\t\tExit, both key and value given will be ignored\n\n");
    sprintf(buffer+strlen(buffer), "HELP:\n");
    sprintf(buffer+strlen(buffer), "\tHELP\t\tType \"help\" to get this information\n");
}

static int com_deal(char *command, char *prmt1, char *prmt2)
{
    int i = 0, com_index, prmt1_index, prmt2_index;
    char com[32];
    
    for (i = 0; command[i] == ' '; i++);
    
    memset(com, 0, sizeof(com));
    
    for (com_index = 0; isalpha(command[i]) && com_index < 31; )
    {
        if (command[i] <= 'z' && command[i] >= 'a')
            command[i] -= 'a' - 'A';

        com[com_index++] = command[i++];
    }
    
    for (; command[i] == ' '; i++);
    
    if (strcmp(com, "OPEN")==0)
    {
        memset(prmt1, 0, sizeof(prmt1));
        for (prmt1_index = 0; isdigit(command[i]) || (command[i] == '.'); )
        {
            prmt1[prmt1_index++] = command[i++];
        }
        prmt1[prmt1_index] = '\0';

        for (; command[i] == ' '; i++);

        memset(prmt2, 0, sizeof(prmt2)); 

        for (prmt2_index = 0; isdigit(command[i]); )
        {
            prmt2[prmt2_index++] = command[i++];
        }
        prmt2[prmt2_index] = '\0';
    } 
    
    else 
    {
        
        memset(prmt1, 0, sizeof(prmt1));
        for (prmt1_index = 0; isalnum(command[i]); )
        {
            prmt1[prmt1_index++] = command[i++];
        }
        prmt1[prmt1_index] = '\0';
        
        for (; command[i] == ' '; i++);       

        memset(prmt2, 0, sizeof(prmt2));
        
        for (prmt2_index = 0; isalnum(command[i]); )
        {
            prmt2[prmt2_index++] = command[i++];
        }
        prmt2[prmt2_index] = '\0';

    }
    
    if (0 == strcmp(com, "OPEN"))
        return OPEN;
    
    if (0 == strcmp(com, "CLOSE"))
        return CLOSE;

    if (0 == strcmp(com, "PUT"))
        return PUT;

    if (0 == strcmp(com, "GET"))
        return GET;
    
    if (0 == strcmp(com, "DELETE"))
        return DELETE;
    
    if (0 == strcmp(com, "EXIT"))
        return EXIT;
    
    return 0;
}
